import * as pty from 'node-pty';
import { v4 as uuidv4 } from 'uuid';
import { TerminalOptions, TerminalSession, ReadOptions, OutputResult, StatsResult } from '../types';
import { OutputBuffer } from './outputBuffer';
import os from 'os';

interface TerminalInstance {
  session: TerminalSession;
  pty: pty.IPty;
  buffer: OutputBuffer;
}

export class TerminalManager {
  private terminals: Map<string, TerminalInstance> = new Map();
  private cleanupInterval: NodeJS.Timeout | null = null;

  constructor() {
    this.startCleanupTimer();
  }

  async createTerminal(options: TerminalOptions = {}): Promise<TerminalSession> {
    if (!options.userId) {
      throw new Error('userId (用户标识) 参数是必填的');
    }

    if (!options.cwd) {
      throw new Error('cwd (工作目录) 参数是必填的');
    }

    const terminalId = uuidv4();
    const userId = options.userId;
    const shell = options.shell || (os.platform() === 'win32' ? 'powershell.exe' : process.env.SHELL || '/bin/bash');
    const cwd = options.cwd;
    const cols = options.cols || 80;
    const rows = options.rows || 24;
    const env = { ...process.env, ...options.env };

    const ptyProcess = pty.spawn(shell, [], {
      name: 'xterm-color',
      cols,
      rows,
      cwd,
      env: env as { [key: string]: string }
    });

    const buffer = new OutputBuffer(parseInt(process.env.MAX_BUFFER_SIZE || '10000'));

    ptyProcess.onData((data) => {
      buffer.append(data);
    });

    const session: TerminalSession = {
      id: terminalId,
      userId,
      pid: ptyProcess.pid,
      shell,
      cwd,
      created: new Date(),
      lastActivity: new Date(),
      status: 'active'
    };

    this.terminals.set(terminalId, {
      session,
      pty: ptyProcess,
      buffer
    });

    return session;
  }

  private checkPermission(terminalId: string, userId: string): void {
    const instance = this.terminals.get(terminalId);
    
    if (!instance) {
      throw new Error('TERMINAL_NOT_FOUND');
    }

    if (instance.session.userId !== userId) {
      throw new Error('PERMISSION_DENIED');
    }
  }

  async writeToTerminal(terminalId: string, userId: string, input: string): Promise<void> {
    this.checkPermission(terminalId, userId);
    
    const instance = this.terminals.get(terminalId);
    
    if (!instance) {
      throw new Error('TERMINAL_NOT_FOUND');
    }

    if (instance.session.status !== 'active') {
      throw new Error('TERMINAL_INACTIVE');
    }

    let processedInput = input;
    if (!input.endsWith('\n') && !input.endsWith('\r')) {
      processedInput = input + '\n';
    }

    instance.pty.write(processedInput);
    instance.session.lastActivity = new Date();
  }

  private cleanAnsiSequences(text: string): string {
    return text.replace(/\x1b\[[0-9;?]*[A-HJKSTfhilmnsu]/g, '')
               .replace(/\x1b\][0-9];[^\x07]*\x07/g, '')
               .replace(/\x1b\[[0-9;]*m/g, '')
               .replace(/\x1b\[[\d;]*[HfABCDsuJKmhlp]/g, '')
               .replace(/\x1b[=>]/g, '')
               .replace(/\x1b\([AB012]/g, '')
               .replace(/\r\n/g, '\n')
               .replace(/\r/g, '\n');
  }

  async readFromTerminal(terminalId: string, userId: string, options: ReadOptions = {}): Promise<OutputResult> {
    this.checkPermission(terminalId, userId);
    
    const instance = this.terminals.get(terminalId);
    
    if (!instance) {
      throw new Error('TERMINAL_NOT_FOUND');
    }

    instance.session.lastActivity = new Date();
    const result = instance.buffer.read(options);

    if (options.clean) {
      const cleanedOutput = this.cleanAnsiSequences(result.output);
      const cleanedBytes = Buffer.byteLength(cleanedOutput, 'utf8');
      result.output = cleanedOutput;
      result.stats.totalBytes = cleanedBytes;
      result.stats.estimatedTokens = Math.ceil(cleanedBytes / 4);
    }

    return result;
  }

  async getTerminalStats(terminalId: string, userId: string): Promise<StatsResult> {
    this.checkPermission(terminalId, userId);
    
    const instance = this.terminals.get(terminalId);
    
    if (!instance) {
      throw new Error('TERMINAL_NOT_FOUND');
    }

    const bufferStats = instance.buffer.getStats();

    return {
      terminalId,
      ...bufferStats,
      isActive: instance.session.status === 'active'
    };
  }

  async getTerminalInfo(terminalId: string, userId: string): Promise<TerminalSession> {
    this.checkPermission(terminalId, userId);
    
    const instance = this.terminals.get(terminalId);
    
    if (!instance) {
      throw new Error('TERMINAL_NOT_FOUND');
    }

    return { ...instance.session };
  }

  async listTerminals(userId?: string): Promise<TerminalSession[]> {
    const allTerminals = Array.from(this.terminals.values()).map(t => ({ ...t.session }));
    
    if (userId) {
      return allTerminals.filter(t => t.userId === userId);
    }
    
    return allTerminals;
  }

  async killTerminal(terminalId: string, userId: string, signal: string = 'SIGTERM'): Promise<void> {
    this.checkPermission(terminalId, userId);
    
    const instance = this.terminals.get(terminalId);
    
    if (!instance) {
      throw new Error('TERMINAL_NOT_FOUND');
    }

    try {
      instance.pty.kill(signal);
      instance.session.status = 'terminated';
      instance.buffer.clear();
      this.terminals.delete(terminalId);
    } catch (error) {
      throw new Error('KILL_FAILED');
    }
  }

  private startCleanupTimer(): void {
    this.cleanupInterval = setInterval(() => {
      this.cleanupTimeoutSessions();
    }, 5 * 60 * 1000);
  }

  private cleanupTimeoutSessions(): void {
    const now = Date.now();
    const timeout = parseInt(process.env.SESSION_TIMEOUT || '86400000');

    for (const [id, instance] of this.terminals.entries()) {
      const inactive = now - instance.session.lastActivity.getTime();
      
      if (inactive > timeout) {
        console.log(`Cleaning up inactive terminal: ${id}`);
        this.killTerminal(id, 'SYSTEM').catch(err => {
          console.error(`Failed to kill terminal ${id}:`, err);
        });
      }
    }
  }

  async shutdown(): Promise<void> {
    if (this.cleanupInterval) {
      clearInterval(this.cleanupInterval);
    }

    const killPromises = Array.from(this.terminals.keys()).map(id =>
      this.killTerminal(id, 'SYSTEM').catch(err => {
        console.error(`Failed to kill terminal ${id} during shutdown:`, err);
      })
    );

    await Promise.all(killPromises);
  }
}
